/*
 * ccvelic.cpp
 *
 *  Created on: 2/10/2012
 *      Author: cvan680
 */



#include "ccvelic.h"
#include "custommodules/ccvethesis/datastructures/mftgtexture.h"
#include "modules/flowreen/datastructures/streamlinetexture.h"
#include "tgt/textureunit.h"
#include "modules/flowreen/utils/flowmath.h"
#include <vector>

#include <windows.h>
#include <math.h>
//using std::vector;
using tgt::TextureUnit;

namespace voreen {

CcveLIC::CcveLIC()
	: ImageProcessor("playwithlic")
, inport0_(Port::INPORT, "volumehandle.volumehandle1")
, inport1_(Port::INPORT, "image.inport1")
, outport_(Port::OUTPORT, "outport")
, kernelSizeProp_("kernelSizeProp", "max. convolution kernel size: ", 7, 1, 2000)
, pixelSamplingProp_("pixelSamplingProp", "pixel sampling: ", 50, 1, 10000)
, useAdaptiveKernelSizeProp_("useAdaptiveKernelSizeProp", "adaptive kernel size: ", false)
, alignmentProp_(0)
, flow2DTexture_(0)
, thresholdProp_("magnitudeThreshold", "threshold for magnitude (%): ", tgt::vec2(0.0f, 100.0f), tgt::vec2(0.0f), tgt::vec2(100.0f))
, sliceIndexProp_("sliceIndex", "slice number: ", 1, 1, 100)
, sliceTexture_(0)
, permutation_(0,1,2)
, privatePort1_(Port::OUTPORT, "image.temp1", false)
, privatePort2_(Port::OUTPORT, "image.temp2", false)
, portGroup_(true)
, ORIGINAL_COUNTER(2)
, counter_(2)
{
	// make sure vertex shader is loaded on ImageProcessor and NOT passthrough.vert
	shaderProp_.setVertexFilename("playwithlic.vert");


	/* REGISTER LIC PROPERTIES */
	alignmentProp_ = new OptionProperty<SliceAlignment>("sliceAlignment", "slice alignment: ");
	alignmentProp_->addOption("xy-plane", "xy-plane", PLANE_XY);
	alignmentProp_->addOption("xz-plane", "xz-plane", PLANE_XZ);
	alignmentProp_->addOption("zy-plane", "zy-plane", PLANE_ZY);
	alignmentProp_->onChange(
		CallMemberAction<CcveLIC>(this, &CcveLIC::onSliceAlignmentChange));

	thresholdProp_.setStepping(tgt::vec2(1.0f));
	thresholdProp_.onChange(CallMemberAction<CcveLIC>(this, &CcveLIC::onThresholdChange));

    // register ports
    addPort(inport0_);
    addPort(inport1_);
    addPort(outport_);


    //addPrivateRenderPort(&privatePort1_);


    addProperty(kernelSizeProp_);
//    addProperty(pixelSamplingProp_);
//    addProperty(useAdaptiveKernelSizeProp_);
//    addProperty(alignmentProp_);
//    addProperty(thresholdProp_);
//    addProperty(sliceIndexProp_);

    mouseEventProp_ = new EventProperty<CcveLIC>(
        "mouseEvent.measure", "Distance measure", this,
        &CcveLIC::mouseEvent,
        tgt::MouseEvent::MOUSE_BUTTON_LEFT,
        tgt::MouseEvent::MOTION | tgt::MouseEvent::PRESSED | tgt::MouseEvent::RELEASED,
        tgt::Event::MODIFIER_NONE, false);

    addEventProperty(mouseEventProp_);
    mouseDown_ = false;
    mouseCurPos2D_ = tgt::ivec2(0, 0);
}


Processor* CcveLIC::create() const {
    return new CcveLIC();
}

CcveLIC::~CcveLIC() {}

void CcveLIC::process() {
    if (getInvalidationLevel() >= Processor::INVALID_PROGRAM){ compile(); }

	tgt::vec2 viewportSize = outport_.getSize();

	bool fixStupidError = false;
	const VolumeFlow3D* volflow = checkVolumeHandleForFlowData<VolumeFlow3D>(inport0_.getData(), fixStupidError);

	if(!volflow){ return; }
	const Flow3D& flow = volflow->getFlow3D();

	tgt::vec2 texDim2D = static_cast<tgt::vec2>(flow.getFlowSliceDimensions(permutation_));
	float textureScaling = getTextureScalingFactor(viewportSize, texDim2D);

	tgt::vec2 outputSize(texDim2D * textureScaling);  // size of the quad on which the texture will be rendered
	tgt::vec2 slicePos = (viewportSize - outputSize) / 2.0f;    // offset for the quad on which the texture will be rendered

	Flow2D flow2D = flow.extractSlice(permutation_, 1);
	const tgt::ivec2 dims(flow2D.dimensions_ * static_cast<int>(textureScaling));

	const tgt::vec2& THRESHOLDS(flow.maxMagnitude_ * tgt::vec2(0.0f, 100.0f) / 100.0f);

	/* as in zcompositor.cpp */
    outport_.activateTarget();
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glDepthFunc(GL_ALWAYS);

    TextureUnit colorUnit1, depthUnit1;
    inport1_.bindTextures(colorUnit1.getEnum(), depthUnit1.getEnum());

    // initialize shader
    program_->activate();

    int numel = dims.x * dims.y;
    float* importanceField = new float[numel];
    memset(importanceField, 0, sizeof(float) * numel);
    for(int i=0; i<numel; i++){
    	if(sqrtf(flow2D.flow2D_[i].x*flow2D.flow2D_[i].x + flow2D.flow2D_[i].y*flow2D.flow2D_[i].y) > 0.3){
    		importanceField[i] = 1.0;
    	}else{
    		importanceField[i] = 0.0;
    	}
    }

	program_->setUniform("flowDimensions_", static_cast<tgt::vec2>(flow2D.dimensions_));
	program_->setUniform("viewportDimensions_", static_cast<tgt::vec2>(viewportSize));

    setGlobalShaderParameters(program_);

    GLuint id1 = 0;
    GLuint id2 = 0;
    id1 = createTexture(reinterpret_cast<GLubyte*>(importanceField), flow2D.dimensions_, GL_LUMINANCE, GL_LUMINANCE);
    id2 = createTexture(reinterpret_cast<GLubyte*>(flow2D.getNormalizedFlow()), flow2D.dimensions_, GL_RGB16, GL_RGB);

    TextureUnit tu1 = new TextureUnit();
    TextureUnit tu2 = new TextureUnit();
    bindTexture(id1, "importanceField_", tu1);
    bindTexture(id2, "vectorField2D_", tu2);

    // binding colour input texture - these should go after all bindTexture calls
    program_->setUniform("colorTex1_", colorUnit1.getUnitNumber());


    program_->setUniform("mouseDown_", mouseDown_);
    program_->setUniform("mouseCoord_", static_cast<tgt::vec2>(mouseCurPos2D_));
    inport1_.setTextureParameters(program_, "textureParameters1_");

    program_->setUniform("kernelSize_", kernelSizeProp_.get());

    renderQuad();

    glDepthFunc(GL_LESS);

    program_->deactivate();
    outport_.deactivateTarget();
    TextureUnit::setZeroUnit();
    LGL_ERROR;
}




void CcveLIC::mouseEvent(tgt::MouseEvent* e) {
	// see modules/base/processors/utility/distancemeasure.cpp for an example implementation of capturing mouse events.
    if (e->action() & tgt::MouseEvent::PRESSED) {
    	mouseDown_ = true;
    	counter_ = 0;
    }
    if (e->action() & tgt::MouseEvent::RELEASED) {
    	mouseDown_ = false;
    	counter_ = 0;
    }
    mouseCurPos2D_ = tgt::ivec2( tgt::ivec2(e->coord().x, e->viewport().y-e->coord().y) );
    e->accept();

    // CCVE: haxx to make sure it runs smoothly on my lab comp - if we're just moving the mouse,
    //		 only update once every 3 triggers. Also update on every click and release.
    counter_ -= 1;
    if(counter_ <= 0){
    	counter_ = ORIGINAL_COUNTER;
    	process();
    }
}


/**
 * FIXME not sure if I need this? Maybe later. Adjusted from flowslicerenderer2 in that I
 * don't actually use the zoomProperty thingy, I just set it to ZOOM_FULL_SIZE in initialization.
 */
float CcveLIC::getTextureScalingFactor(const tgt::vec2& viewportSize,
                                             const tgt::vec2& textureSize)
{
    float scaleWidth = viewportSize.x / textureSize.x;
    float scaleHeight = viewportSize.y / textureSize.y;
    float scaling = (scaleWidth <= scaleHeight) ? scaleWidth : scaleHeight;
    if (zoom_ != ZOOM_FULL_SIZE) {
        // find minimal scaling factor
        //
        float zoom = static_cast<float>(zoom_);
        if (zoom <= scaling)
            scaling = zoom;
        else {
            zoom_ = ZOOM_FULL_SIZE;
        }
    }
    return scaling;
}

void CcveLIC::onThresholdChange() {
    tgt::vec2 threshold(thresholdProp_.get());
    if (threshold.x > threshold.y)
        threshold.y = threshold.x;
    thresholdProp_.set(threshold);
}

void CcveLIC::onSliceAlignmentChange() {
    alignment_ = alignmentProp_->getValue();
    permutation_ = getCoordinatePermutation(alignment_);
    //rebuildTexture_ = true;
    //updateNumSlices();
    //rebuildShader();
}

tgt::ivec3 CcveLIC::getCoordinatePermutation(const SliceAlignment& alignment) {
    switch (alignment) {
        case PLANE_XY:
            return tgt::ivec3(0, 1, 2);
        case PLANE_XZ:
            return tgt::ivec3(0, 2, 1);
        case PLANE_ZY:
            return tgt::ivec3(2, 1, 0);
    }
    return tgt::ivec3(0, 0, 0);
}


void CcveLIC::initialize() throw (VoreenException) {
    // call superclass function first
    ImageProcessor::initialize();

    zoom_ = ZOOM_FULL_SIZE;

    if(!loadShader("passthrough.vert", "copyimage.frag")){
    	std::cout << "\nERR: Something went wrong loading shaders passthrough.vert and copyimage.frag\n";
    }
    ImageProcessor::initialize();
}

void CcveLIC::deinitialize() throw (VoreenException) {
    ImageProcessor::deinitialize();
    ShdrMgr.dispose(shader_);
    shader_ = 0;
}


void CcveLIC::compile() {
    if (program_){ program_->setHeaders( generateHeader()); }
    ImageProcessor::compile();
}



/* HELPERS TAKEN FROM FLOWSLICERENDERER */
bool CcveLIC::loadShader(const std::string& vertexShaderName, const std::string& fragmentShaderName)
{
    if (shader_ == 0) {
        try {
            shader_ = ShdrMgr.loadSeparate(vertexShaderName.c_str(), fragmentShaderName.c_str(), generateShaderHeader(), false);
        } catch(tgt::Exception) {
            //LERROR("Failed to load shaders '" <<  vertexShaderName << "' & '" << fragmentShaderName << "'!");
        	std::cout << "\n\nSOMETHING WENT WRONG HERE!\n\n";
            return false;
        }
        rebuildShader();
    }
    return true;
}

bool CcveLIC::rebuildShader() {
    if (shader_ == 0)
        return false;

    shader_->deactivate();
    shader_->setHeaders(generateShaderHeader());
    return shader_->rebuild();
}

std::string CcveLIC::generateShaderHeader() {
	return ImageProcessor::generateHeader();
}

void CcveLIC::beginOrthographicRendering(const tgt::ivec2& viewport) const {
    glPushAttrib(GL_ALL_ATTRIB_BITS);

    // Set a viewport of the size of the generated texture: this is ugly but still better
    // than resizing the entire texture container twice. Shaders have to take the scaling
    // of the viewport into account when calculating texture coordinates (see Spot Noise)
    //
    glViewport(0, 0, viewport.x, viewport.y);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0.0, viewport.x, 0.0, viewport.y, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    glDisable(GL_DEPTH_TEST);
}

void CcveLIC::endOrthographicRendering() const {

    glEnable(GL_DEPTH_TEST);

    glPopAttrib();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
}

tgt::Texture* CcveLIC::copyTargetToTexture(RenderPort& renderPort, const tgt::vec2& textureSize) const {
    renderPort.activateTarget();

    //glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    tgt::ivec2 size = static_cast<tgt::ivec2>(textureSize);
    tgt::Texture* texture = new tgt::Texture(tgt::vec3(textureSize, 1), GL_RGBA, GL_RGBA16,
        GL_FLOAT, tgt::Texture::NEAREST);
    glReadPixels(0, 0, size.x, size.y, GL_RGBA, GL_FLOAT, texture->getPixelData());

    //renderPort.deactivateTarget();
    return texture;
}


// reinterpret_cast<GLubyte*>(data)
GLuint CcveLIC::createTexture(GLubyte* data, tgt::ivec2 dims, GLint internalformat, GLenum format){
	GLuint id = 0;
	glGenTextures(1, &id);
    glBindTexture(GL_TEXTURE_2D, id);
    glTexImage2D(GL_TEXTURE_2D, 0, internalformat, dims.x, dims.y, 0, format, GL_FLOAT, data);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    return id;
}

void CcveLIC::bindTexture(GLuint id, string uniform, TextureUnit texunit){
    glActiveTexture(texunit.getEnum());
    glBindTexture(GL_TEXTURE_2D, id);
	program_->setUniform(uniform, texunit.getUnitNumber());
}




void CcveLIC::prepareFlowTexture2D(const Flow2D& flow2D) {
	//if(flow2DTexture_){ delete flow2DTexture_; }
	delete flow2DTexture_;
    flow2DTexture_ = 0;
    tgt::vec3* pixels = flow2D.getNormalizedFlow();
    flow2DTexture_ = new tgt::Texture(reinterpret_cast<GLubyte*>(pixels),
        tgt::ivec3(flow2D.dimensions_, 1), GL_RGB, GL_RGB16, GL_FLOAT, tgt::Texture::NEAREST);

    flow2DTexture_->uploadTexture();

    // this messed stuff up.. it is already bound in uploadTexture call
    //glBindTexture(GL_TEXTURE_2D, 0);

/*
    TextureUnit vfUnit;
    glActiveTexture(vfUnit.getEnum());
    glBindTexture(GL_TEXTURE_2D, flow2DTexture_->getId());
    */
//	program_->setUniform("vectorField2D_", vfUnit.getUnitNumber());
	/*
    std::cout << "\nvectorField2D_ TextureUnit enum: " << vfUnit.getEnum();
    std::cout << "\nvectorField2D_ TextureUnit unitnumber: " << vfUnit.getUnitNumber();
    */
    //GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle, UniformName), textureUnit - TextureUnit.Texture0);
/*

    TextureUnit cu2;
    glActiveTexture(cu2.getEnum());
    flow2DTexture_->bind();
	program_->setUniform("vectorField2D_", cu2.getUnitNumber());
	*/
}

void CcveLIC::uploadTextureToShader(tgt::Texture* tex){
	// TODO once I get this working: make it dynamic, so we can upload an arbitrary number of
	//		textures; i.e. also make the uniform var name dynamic.

	// see http://www.opentk.com/node/2559 for explanation of uploading multiple textures

	//GL.GenTextures(1, _id);
	//GL.BindTexture(TextureTarget.Texture2D, texture);
	//GL.TexImage2D(all kinds of stuff);
	tex->uploadTexture();

	// TODO need this? below the sites GL call is the glew equivalent (see glew.h)
	//GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
	//GLAPI void GLAPIENTRY glTexParameteri (GLenum target, GLenum pname, GLint param);

    TextureUnit cu1;
    glActiveTexture(cu1.getEnum()); // GL.ActiveTexture(textureUnit);
    tex->bind(); // GL.BindTexture(TextureTarget.Texture2D, textureId);

    //GL.Uniform1(GL.GetUniformLocation(shaderProgramHandle, UniformName), textureUnit - TextureUnit.Texture0);
	program_->setUniform("importanceField2D_", cu1.getUnitNumber());
    std::cout << "\nimportanceTexture TextureUnit enum: " << cu1.getEnum();
    std::cout << "\nimportanceTexture TextureUnit unitnumber: " << cu1.getUnitNumber();

}

}//namespace

